/**
 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "js_eval.h"
#include "ecma_string.h"
#include "include/mem/panda_string.h"
#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/internal_call_params.h"
#include "plugins/ecmascript/runtime/interpreter/interpreter-inl.h"
#include "es2panda.h"
#include "assembler/assembly-emitter.h"
#include "util/options.h"
#include "util/diagnosticEngine.h"

namespace ark::ecmascript {
JSTaggedValue EvalUtils::GetEvaluatedScript(JSThread *thread, const JSHandle<EcmaString> &str,
                                            const es2panda::util::Options &options,
                                            es2panda::util::DiagnosticEngine &diagnosticEngine, uint32_t parserStatus)
{
    auto buffer = str->GetCString();
    es2panda::Compiler compiler(es2panda::ScriptExtension::JS);
    es2panda::SourceFile input("eval.js", std::string_view(buffer.get()), false);
    std::unique_ptr<pandasm::Program> program {compiler.Compile(input, options, diagnosticEngine, parserStatus)};
    ObjectFactory *objectFactory = thread->GetEcmaVM()->GetFactory();

    if (!program) {
        const auto &err = compiler.GetError();
        JSHandle<JSObject> error = objectFactory->GetJSError(ErrorType::SYNTAX_ERROR, err.Message().c_str());
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error.GetTaggedValue(), JSTaggedValue::Exception());
    }

    panda_file::MemoryWriter writer;
    [[maybe_unused]] bool status = pandasm::AsmEmitter::Emit(&writer, *program);
    ASSERT(status);
    const auto &data = writer.GetData();

    auto pf = panda_file::OpenPandaFileFromMemory(data.data(), data.size());
    ASSERT(pf);

    auto *pandaFile = pf.get();
    auto *ecmaVm = thread->GetEcmaVM();

    ecmaVm->AddPandaFile(pandaFile, false);
    Runtime::GetCurrent()->GetClassLinker()->AddPandaFile(std::move(pf));

    auto resolveFunc = ecmaVm->GetInvocableFunction(*pandaFile, "func_main_0");
    ASSERT(resolveFunc.HasValue());

    JSHandle<JSFunction> func(thread, resolveFunc.Value());
    // For DirectEval, functions in envirement and in arguments are different.
    // Compiled code use function form the parameter for creating envirement.
    // So we temporary disable compilation for eval calls.
    auto method = func->GetCallTarget();
    ASSERT(method->GetCompilationStatus() == Method::NOT_COMPILED);
    method->AtomicSetCompilationStatus(Method::NOT_COMPILED, Method::FAILED);
    ASSERT(method->GetCompilationStatus() == Method::FAILED);

    return resolveFunc.Value();
}

JSTaggedValue EvalUtils::DirectEval(JSThread *thread, uint32_t parserStatus, JSTaggedValue arg0,
                                    JSTaggedValue evalbindings)
{
    [[maybe_unused]] EcmaHandleScope scope(thread);

    JSHandle<JSTaggedValue> arg0Handle(thread, arg0);
    JSHandle<JSTaggedValue> evalBindingsHandle(thread, evalbindings);
    JSHandle<EcmaString> argStr = JSTaggedValue::ToString(thread, arg0Handle);
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, argStr.GetTaggedValue());

    auto diagnosticEngine = es2panda::util::DiagnosticEngine();
    es2panda::util::Options options("", diagnosticEngine);
    options.SetEvalMode("direct");

    JSHandle<JSFunction> func(thread, GetEvaluatedScript(thread, argStr, options, diagnosticEngine, parserStatus));
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, func.GetTaggedValue());
    uint32_t index = 0;

    JSHandle<JSTaggedValue> paramFunc = JSArray::FastGetPropertyByValue(thread, evalBindingsHandle, index++);
    JSHandle<JSTaggedValue> newTarget = JSArray::FastGetPropertyByValue(thread, evalBindingsHandle, index++);
    JSHandle<JSTaggedValue> thisValue = JSArray::FastGetPropertyByValue(thread, evalBindingsHandle, index++);
    JSHandle<JSTaggedValue> lexicalContext = JSArray::FastGetPropertyByValue(thread, evalBindingsHandle, index++);

    ASSERT(1 == index - js_method_args::NUM_MANDATORY_ARGS);
    auto info = NewRuntimeCallInfo(thread, paramFunc, thisValue, newTarget, 1);
    info->SetCallArgs(lexicalContext);

    auto res = EcmaInterpreter::ExecuteInEnv(info.Get(), func.GetTaggedValue());
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
    return res;
}

JSTaggedValue EvalUtils::Eval(JSThread *thread, const JSHandle<JSTaggedValue> &arg0)
{
    [[maybe_unused]] EcmaHandleScope scope(thread);

    JSHandle<EcmaString> argStr = JSTaggedValue::ToString(thread, arg0);
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, argStr.GetTaggedValue());

    auto diagnosticEngine = es2panda::util::DiagnosticEngine();
    es2panda::util::Options options("", diagnosticEngine);
    options.SetEvalMode("default");
    JSHandle<JSFunction> func(thread, GetEvaluatedScript(thread, argStr, options, diagnosticEngine));
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, func.GetTaggedValue());

    JSHandle<JSTaggedValue> thisValue(thread, thread->GetGlobalObject());
    auto info = NewRuntimeCallInfo(thread, func, thisValue, JSTaggedValue::Undefined(), 0);
    return EcmaInterpreter::Execute(info.Get());
}

JSTaggedValue EvalUtils::CreateDynamicFunction(EcmaRuntimeCallInfo *argv, DynamicFunctionKind kind)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> functionBody(thread, JSTaggedValue::Undefined());
    JSHandle<EcmaString> functionParamStr = factory->GetEmptyString();
    uint32_t nargs = argv->GetArgsNumber();

    if (nargs == 1) {
        functionBody = builtins_common::GetCallArg(argv, 0);
    } else if (nargs > 1) {
        JSHandle<JSTaggedValue> firstParamHandle = builtins_common::GetCallArg(argv, 0);
        functionParamStr = JSTaggedValue::ToString(thread, firstParamHandle);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

        uint32_t iter = 1;

        while (iter < nargs - 1) {
            JSHandle<EcmaString> commaStr = factory->NewFromCanBeCompressString(",");
            functionParamStr = factory->ConcatFromString(functionParamStr, commaStr);
            JSHandle<JSTaggedValue> paramHandle = builtins_common::GetCallArg(argv, iter);
            JSHandle<EcmaString> paramString = JSTaggedValue::ToString(thread, paramHandle);
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
            functionParamStr = factory->ConcatFromString(functionParamStr, paramString);
            iter++;
        }

        functionBody = builtins_common::GetCallArg(argv, iter);
    }

    JSHandle<EcmaString> resultStr = factory->NewFromCanBeCompressString("(");
    JSHandle<EcmaString> functionBodyStr = factory->GetEmptyString();

    if (!functionBody->IsUndefined()) {
        functionBodyStr = JSTaggedValue::ToString(thread, functionBody);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    }

    if (kind == DynamicFunctionKind::ASYNC || kind == DynamicFunctionKind::ASYNC_GENERATOR) {
        // '(async function'
        JSHandle<EcmaString> asyncStr = factory->NewFromCanBeCompressString("async ");
        resultStr = factory->ConcatFromString(resultStr, asyncStr);
    }

    // '(function'
    JSHandle<EcmaString> functionStr = factory->NewFromCanBeCompressString("function ");
    resultStr = factory->ConcatFromString(resultStr, functionStr);

    if (kind == DynamicFunctionKind::GENERATOR || kind == DynamicFunctionKind::ASYNC_GENERATOR) {
        // '(function*'
        JSHandle<EcmaString> asteriskStr = factory->NewFromCanBeCompressString("* ");
        resultStr = factory->ConcatFromString(resultStr, asteriskStr);
    }

    // '(function anonymous('
    JSHandle<EcmaString> anonymousStr = factory->NewFromCanBeCompressString("anonymous(");
    resultStr = factory->ConcatFromString(resultStr, anonymousStr);

    // '(function anonymous(params'
    resultStr = factory->ConcatFromString(resultStr, functionParamStr);

    // '(function anonymous(params){'
    JSHandle<EcmaString> rightParenLeftBrace = factory->NewFromCanBeCompressString("){");
    resultStr = factory->ConcatFromString(resultStr, rightParenLeftBrace);

    // '(function anonymous(params) {body'
    resultStr = factory->ConcatFromString(resultStr, functionBodyStr);

    // '(function anonymous(params) {body})'
    JSHandle<EcmaString> rightBraceRightParen = factory->NewFromCanBeCompressString("})");
    resultStr = factory->ConcatFromString(resultStr, rightBraceRightParen);

    auto diagnosticEngine = es2panda::util::DiagnosticEngine();
    es2panda::util::Options options("", diagnosticEngine);
    options.SetEvalMode("function");
    JSHandle<JSFunction> func(thread, GetEvaluatedScript(thread, resultStr, options, diagnosticEngine));
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, func.GetTaggedValue());

    JSHandle<JSTaggedValue> thisValue(thread, thread->GetGlobalObject());

    auto info = NewRuntimeCallInfo(thread, func, thisValue, JSTaggedValue::Undefined(), 0);
    JSHandle<JSTaggedValue> res(thread, EcmaInterpreter::Execute(info.Get()));

    ASSERT(res->IsCallable());

    JSHandle<JSTaggedValue> newTargetHandle = argv->GetNewTarget();

    if (newTargetHandle->IsUndefined()) {
        return res.GetTaggedValue();
    }

    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
    JSHandle<JSTaggedValue> newTargetProto =
        JSTaggedValue::GetProperty(thread, newTargetHandle, globalConst->GetHandledPrototypeString()).GetValue();

    if (newTargetProto->IsECMAObject()) {
        JSObject::SetPrototype(thread, JSHandle<JSObject>::Cast(res), newTargetProto);
    }

    return res.GetTaggedValue();
}
}  // namespace ark::ecmascript
